/*
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is MPEG4IP.
 *
 * The Initial Developer of the Original Code is Cisco Systems Inc.
 * Portions created by Cisco Systems Inc. are
 * Copyright (C) Cisco Systems Inc. 2001.  All Rights Reserved.
 *
 * Contributor(s):
 *      Dave Mackie     dmackie@cisco.com
 *      Kona Blend      kona8lend@@gmail.com
 */

#include "src/impl.h"

namespace mp4v2
{
    namespace impl
    {

        ///////////////////////////////////////////////////////////////////////////////

        MP4Property::MP4Property(MP4Atom &parentAtom, const char *name)
            : m_parentAtom(parentAtom)
        {
            m_name = name;
            m_readOnly = false;
            m_implicit = false;
        }

        bool MP4Property::FindProperty(const char *name,
                                       MP4Property **ppProperty, uint32_t *pIndex)
        {
            if (name == NULL)
            {
                return false;
            }

            if (!strcasecmp(m_name, name))
            {
                log.verbose1f("\"%s\": FindProperty: matched %s",
                              m_parentAtom.GetFile().GetFilename().c_str(), name);
                *ppProperty = this;
                return true;
            }
            return false;
        }

        // Integer Property

        uint64_t MP4IntegerProperty::GetValue(uint32_t index)
        {
            switch (this->GetType())
            {
            case Integer8Property:
                return ((MP4Integer8Property *)this)->GetValue(index);
            case Integer16Property:
                return ((MP4Integer16Property *)this)->GetValue(index);
            case Integer24Property:
                return ((MP4Integer24Property *)this)->GetValue(index);
            case Integer32Property:
                return ((MP4Integer32Property *)this)->GetValue(index);
            case Integer64Property:
                return ((MP4Integer64Property *)this)->GetValue(index);
            default:
                ASSERT(false);
            }
            return (0);
        }

        void MP4IntegerProperty::SetValue(uint64_t value, uint32_t index)
        {
            switch (this->GetType())
            {
            case Integer8Property:
                ((MP4Integer8Property *)this)->SetValue(value, index);
                break;
            case Integer16Property:
                ((MP4Integer16Property *)this)->SetValue(value, index);
                break;
            case Integer24Property:
                ((MP4Integer24Property *)this)->SetValue(value, index);
                break;
            case Integer32Property:
                ((MP4Integer32Property *)this)->SetValue(value, index);
                break;
            case Integer64Property:
                ((MP4Integer64Property *)this)->SetValue(value, index);
                break;
            default:
                ASSERT(false);
            }
        }

        void MP4IntegerProperty::InsertValue(uint64_t value, uint32_t index)
        {
            switch (this->GetType())
            {
            case Integer8Property:
                ((MP4Integer8Property *)this)->InsertValue(value, index);
                break;
            case Integer16Property:
                ((MP4Integer16Property *)this)->InsertValue(value, index);
                break;
            case Integer24Property:
                ((MP4Integer24Property *)this)->InsertValue(value, index);
                break;
            case Integer32Property:
                ((MP4Integer32Property *)this)->InsertValue(value, index);
                break;
            case Integer64Property:
                ((MP4Integer64Property *)this)->InsertValue(value, index);
                break;
            default:
                ASSERT(false);
            }
        }

        void MP4IntegerProperty::DeleteValue(uint32_t index)
        {
            switch (this->GetType())
            {
            case Integer8Property:
                ((MP4Integer8Property *)this)->DeleteValue(index);
                break;
            case Integer16Property:
                ((MP4Integer16Property *)this)->DeleteValue(index);
                break;
            case Integer24Property:
                ((MP4Integer24Property *)this)->DeleteValue(index);
                break;
            case Integer32Property:
                ((MP4Integer32Property *)this)->DeleteValue(index);
                break;
            case Integer64Property:
                ((MP4Integer64Property *)this)->DeleteValue(index);
                break;
            default:
                ASSERT(false);
            }
        }

        void MP4IntegerProperty::IncrementValue(int32_t increment, uint32_t index)
        {
            SetValue(GetValue() + increment);
        }

        void MP4Integer8Property::Dump(uint8_t indent,
                                       bool dumpImplicits, uint32_t index)
        {
            if (m_implicit && !dumpImplicits)
            {
                return;
            }
            if (index != 0)
                log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s[%u] = %u (0x%02x)",
                         m_parentAtom.GetFile().GetFilename().c_str(),
                         m_name, index, m_values[index], m_values[index]);
            else
                log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s = %u (0x%02x)",
                         m_parentAtom.GetFile().GetFilename().c_str(),
                         m_name, m_values[index], m_values[index]);
        }

        void MP4Integer16Property::Dump(uint8_t indent,
                                        bool dumpImplicits, uint32_t index)
        {
            if (m_implicit && !dumpImplicits)
            {
                return;
            }
            if (index != 0)
                log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s[%u] = %u (0x%04x)",
                         m_parentAtom.GetFile().GetFilename().c_str(),
                         m_name, index, m_values[index], m_values[index]);
            else
                log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s = %u (0x%04x)",
                         m_parentAtom.GetFile().GetFilename().c_str(),
                         m_name, m_values[index], m_values[index]);
        }

        void MP4Integer24Property::Dump(uint8_t indent,
                                        bool dumpImplicits, uint32_t index)
        {
            if (m_implicit && !dumpImplicits)
            {
                return;
            }
            if (index != 0)
                log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s[%u] = %u (0x%06x)",
                         m_parentAtom.GetFile().GetFilename().c_str(),
                         m_name, index, m_values[index], m_values[index]);
            else
                log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s = %u (0x%06x)",
                         m_parentAtom.GetFile().GetFilename().c_str(),
                         m_name, m_values[index], m_values[index]);
        }

        void MP4Integer32Property::Dump(uint8_t indent,
                                        bool dumpImplicits, uint32_t index)
        {
            if (m_implicit && !dumpImplicits)
            {
                return;
            }
            if (index != 0)
                log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s[%u] = %u (0x%08x)",
                         m_parentAtom.GetFile().GetFilename().c_str(),
                         m_name, index, m_values[index], m_values[index]);
            else
                log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s = %u (0x%08x)",
                         m_parentAtom.GetFile().GetFilename().c_str(),
                         m_name, m_values[index], m_values[index]);
        }

        void MP4Integer64Property::Dump(uint8_t indent,
                                        bool dumpImplicits, uint32_t index)
        {
            if (m_implicit && !dumpImplicits)
            {
                return;
            }
            if (index != 0)
                log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s[%u] = %" PRIu64 " (0x%016" PRIx64 ")",
                         m_parentAtom.GetFile().GetFilename().c_str(),
                         m_name, index, m_values[index], m_values[index]);
            else
                log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s = %" PRIu64 " (0x%016" PRIx64 ")",
                         m_parentAtom.GetFile().GetFilename().c_str(),
                         m_name, m_values[index], m_values[index]);
        }

        // MP4BitfieldProperty

        void MP4BitfieldProperty::Read(MP4File &file, uint32_t index)
        {
            if (m_implicit)
            {
                return;
            }
            m_values[index] = file.ReadBits(m_numBits);
        }

        void MP4BitfieldProperty::Write(MP4File &file, uint32_t index)
        {
            if (m_implicit)
            {
                return;
            }
            file.WriteBits(m_values[index], m_numBits);
        }

        void MP4BitfieldProperty::Dump(uint8_t indent,
                                       bool dumpImplicits, uint32_t index)
        {
            if (m_implicit && !dumpImplicits)
            {
                return;
            }
            uint8_t hexWidth = m_numBits / 4;
            if (hexWidth == 0 || (m_numBits % 4))
            {
                hexWidth++;
            }
            if (index != 0)
                log.dump(indent, MP4_LOG_VERBOSE1,
                         "\"%s\": %s[%u] = %" PRIu64 " (0x%0*" PRIx64 ") <%u bits>",
                         m_parentAtom.GetFile().GetFilename().c_str(),
                         m_name, index, m_values[index], (int)hexWidth, m_values[index], m_numBits);
            else
                log.dump(indent, MP4_LOG_VERBOSE1,
                         "\"%s\": %s = %" PRIu64 " (0x%0*" PRIx64 ") <%u bits>",
                         m_parentAtom.GetFile().GetFilename().c_str(),
                         m_name, m_values[index], (int)hexWidth, m_values[index], m_numBits);
        }

        // MP4Float32Property

        void MP4Float32Property::Read(MP4File &file, uint32_t index)
        {
            if (m_implicit)
            {
                return;
            }
            if (m_useFixed16Format)
            {
                m_values[index] = file.ReadFixed16();
            }
            else if (m_useFixed32Format)
            {
                m_values[index] = file.ReadFixed32();
            }
            else
            {
                m_values[index] = file.ReadFloat();
            }
        }

        void MP4Float32Property::Write(MP4File &file, uint32_t index)
        {
            if (m_implicit)
            {
                return;
            }
            if (m_useFixed16Format)
            {
                file.WriteFixed16(m_values[index]);
            }
            else if (m_useFixed32Format)
            {
                file.WriteFixed32(m_values[index]);
            }
            else
            {
                file.WriteFloat(m_values[index]);
            }
        }

        void MP4Float32Property::Dump(uint8_t indent,
                                      bool dumpImplicits, uint32_t index)
        {
            if (m_implicit && !dumpImplicits)
            {
                return;
            }
            if (index != 0)
                log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s[%u] = %f",
                         m_parentAtom.GetFile().GetFilename().c_str(),
                         m_name, index, m_values[index]);
            else
                log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s = %f",
                         m_parentAtom.GetFile().GetFilename().c_str(),
                         m_name, m_values[index]);
        }

        // MP4StringProperty

        MP4StringProperty::MP4StringProperty(
            MP4Atom &parentAtom,
            const char *name,
            bool        useCountedFormat,
            bool        useUnicode,
            bool        arrayMode )

            : MP4Property( parentAtom, name )
            , m_arrayMode        ( arrayMode )
            , m_useCountedFormat ( useCountedFormat )
            , m_useExpandedCount ( false )
            , m_useUnicode       ( useUnicode )
            , m_fixedLength      ( 0 )
        {
            SetCount( 1 );
            m_values[0] = NULL;
        }

        MP4StringProperty::~MP4StringProperty()
        {
            uint32_t count = GetCount();
            for (uint32_t i = 0; i < count; i++)
            {
                MP4Free(m_values[i]);
            }
        }

        void MP4StringProperty::SetCount(uint32_t count)
        {
            uint32_t oldCount = m_values.Size();

            m_values.Resize(count);

            for (uint32_t i = oldCount; i < count; i++)
            {
                m_values[i] = NULL;
            }
        }

        void MP4StringProperty::SetValue(const char *value, uint32_t index)
        {
            if (m_readOnly)
            {
                ostringstream msg;
                msg << "property " << m_name << "is read-only";
                throw new PlatformException(msg.str().c_str(), EACCES, __FILE__, __LINE__, __FUNCTION__ );
            }

            MP4Free(m_values[index]);

            if (m_fixedLength)
            {
                m_values[index] = (char *)MP4Calloc(m_fixedLength + 1);
                if (value)
                {
                    strncpy(m_values[index], value, m_fixedLength);
                }
            }
            else
            {
                if (value)
                {
                    m_values[index] = MP4Stralloc(value);
                }
                else
                {
                    m_values[index] = NULL;
                }
            }
        }

        void MP4StringProperty::Read( MP4File &file, uint32_t index )
        {
            if( m_implicit )
                return;

            uint32_t begin = index;
            uint32_t max   = index + 1;

            if( m_arrayMode )
            {
                begin = 0;
                max   = GetCount();
            }

            for( uint32_t i = begin; i < max; i++ )
            {
                char *&value = m_values[i];

                // Generally a default atom setting, e.g. see atom_avc1.cpp, "JVT/AVC Coding"; we'll leak this string if
                // we don't free.  Note that MP4Free checks for null.
                MP4Free(value);

                if( m_useCountedFormat )
                {
                    value = file.ReadCountedString( (m_useUnicode ? 2 : 1), m_useExpandedCount, m_fixedLength );
                }
                else if( m_fixedLength )
                {
                    value = (char *)MP4Calloc( m_fixedLength + 1 );
                    file.ReadBytes( (uint8_t *)value, m_fixedLength );
                }
                else
                {
                    value = file.ReadString();
                }
            }
        }

        void MP4StringProperty::Write( MP4File &file, uint32_t index )
        {
            if( m_implicit )
                return;

            uint32_t begin = index;
            uint32_t max   = index + 1;

            if( m_arrayMode )
            {
                begin = 0;
                max   = GetCount();
            }

            for( uint32_t i = begin; i < max; i++ )
            {
                char *&value = m_values[i];

                if( m_useCountedFormat )
                {
                    file.WriteCountedString( value, (m_useUnicode ? 2 : 1), m_useExpandedCount, m_fixedLength );
                }
                else if( m_fixedLength )
                {
                    file.WriteBytes( (uint8_t *)value, m_fixedLength );
                }
                else
                {
                    file.WriteString( value );
                }
            }
        }

        void MP4StringProperty::Dump( uint8_t indent, bool dumpImplicits, uint32_t index )
        {
            if( m_implicit && !dumpImplicits )
                return;

            if( !m_arrayMode )
            {
                char indexd[32];
                if( index != 0 )
                    snprintf( indexd, 32, "[%u]", index );
                else
                    indexd[0] = '\0';

                if( m_useUnicode )
                    log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s%s = %ls",
                             m_parentAtom.GetFile().GetFilename().c_str(),
                             m_name, indexd, (wchar_t *)m_values[index] );
                else
                    log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s%s = %s",
                             m_parentAtom.GetFile().GetFilename().c_str(),
                             m_name, indexd, m_values[index] );
            }
            else if( log.verbosity >= MP4_LOG_VERBOSE2 )
            {
                const uint32_t max = GetCount();

                log.dump(indent, MP4_LOG_VERBOSE2, "\"%s\": %s (size=%u)",
                         m_parentAtom.GetFile().GetFilename().c_str(),
                         m_name, max );

                for( uint32_t i = 0; i < max; i++ )
                {
                    char *&value = m_values[i];

                    if( m_useUnicode )
                        log.dump(indent, MP4_LOG_VERBOSE2, "\"%s\": %s[%u] = %ls",
                                 m_parentAtom.GetFile().GetFilename().c_str(),
                                 m_name, i, (wchar_t *)value );
                    else
                        log.dump(indent, MP4_LOG_VERBOSE2, "\"%s\": %s[%u] = %s",
                                 m_parentAtom.GetFile().GetFilename().c_str(),
                                 m_name, i, value );
                }
            }
            else
            {
                log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": <table entries suppressed>",
                         m_parentAtom.GetFile().GetFilename().c_str() );
            }
        }

        // MP4BytesProperty

        MP4BytesProperty::MP4BytesProperty(MP4Atom &parentAtom, const char *name, uint32_t valueSize,
                                           uint32_t defaultValueSize)
            : MP4Property(parentAtom, name)
            , m_fixedValueSize(0)
            , m_defaultValueSize(defaultValueSize)
        {
            SetCount(1);
            m_values[0] = (uint8_t *)MP4Calloc(valueSize);
            m_valueSizes[0] = valueSize;
        }

        MP4BytesProperty::~MP4BytesProperty()
        {
            uint32_t count = GetCount();
            for (uint32_t i = 0; i < count; i++)
            {
                MP4Free(m_values[i]);
            }
        }

        void MP4BytesProperty::SetCount(uint32_t count)
        {
            uint32_t oldCount = m_values.Size();

            m_values.Resize(count);
            m_valueSizes.Resize(count);

            for (uint32_t i = oldCount; i < count; i++)
            {
                m_values[i] = NULL;
                m_valueSizes[i] = m_defaultValueSize;
            }
        }

        void MP4BytesProperty::SetValue(const uint8_t *pValue, uint32_t valueSize,
                                        uint32_t index)
        {
            if (m_readOnly)
            {
                ostringstream msg;
                msg << "property " << m_name << "is read-only";
                throw new PlatformException(msg.str().c_str(), EACCES, __FILE__, __LINE__, __FUNCTION__ );
            }
            if (m_fixedValueSize)
            {
                if (valueSize > m_fixedValueSize)
                {
                    ostringstream msg;
                    msg << GetParentAtom().GetType() << "." << GetName() << " value size " << valueSize << " exceeds fixed value size " << m_fixedValueSize;
                    throw new Exception(msg.str().c_str(), __FILE__, __LINE__, __FUNCTION__ );
                }
                if (m_values[index] == NULL)
                {
                    m_values[index] = (uint8_t *)MP4Calloc(m_fixedValueSize);
                    m_valueSizes[index] = m_fixedValueSize;
                }
                if (pValue)
                {
                    memcpy(m_values[index], pValue, valueSize);
                }
            }
            else
            {
                MP4Free(m_values[index]);
                if (pValue)
                {
                    m_values[index] = (uint8_t *)MP4Malloc(valueSize);
                    memcpy(m_values[index], pValue, valueSize);
                    m_valueSizes[index] = valueSize;
                }
                else
                {
                    m_values[index] = NULL;
                    m_valueSizes[index] = 0;
                }
            }
        }

        void MP4BytesProperty::SetValueSize(uint32_t valueSize, uint32_t index)
        {
            if (m_fixedValueSize)
            {
                throw new Exception("can't change size of fixed sized property",
                                    __FILE__, __LINE__, __FUNCTION__ );
            }
            if (m_values[index] != NULL)
            {
                m_values[index] = (uint8_t *)MP4Realloc(m_values[index], valueSize);
            }
            m_valueSizes[index] = valueSize;
        }

        void MP4BytesProperty::SetFixedSize(uint32_t fixedSize)
        {
            m_fixedValueSize = 0;
            for (uint32_t i = 0; i < GetCount(); i++)
            {
                SetValueSize(fixedSize, i);
            }
            m_fixedValueSize = fixedSize;
        }

        void MP4BytesProperty::Read(MP4File &file, uint32_t index)
        {
            if (m_implicit)
            {
                return;
            }
            MP4Free(m_values[index]);
            m_values[index] = (uint8_t *)MP4Malloc(m_valueSizes[index]);
            file.ReadBytes(m_values[index], m_valueSizes[index]);
        }

        void MP4BytesProperty::Write(MP4File &file, uint32_t index)
        {
            if (m_implicit)
            {
                return;
            }
            file.WriteBytes(m_values[index], m_valueSizes[index]);
        }

        void MP4BytesProperty::Dump(uint8_t indent,
                                    bool dumpImplicits, uint32_t index)
        {
            if( m_implicit && !dumpImplicits )
                return;

            const uint32_t size  = m_valueSizes[index];
            const uint8_t *const value = m_values[index];

            if( size == 0 )
            {
                log.dump(indent, MP4_LOG_VERBOSE2, "\"%s\": %s = <%u bytes>",
                         m_parentAtom.GetFile().GetFilename().c_str(),
                         m_name, size );
                return;
            }

            if( size <= 16 )
            {
                ostringstream oss;
                ostringstream text;

                oss << "  ";
                for( uint32_t i = 0; i < size; i++ )
                {
                    if( i )
                        oss << ' ';
                    oss << hex << setw(2) << setfill('0') << right << static_cast<uint32_t>(value[i]);
                    text << (isprint( static_cast<int>(value[i]) ) ? static_cast<char>(value[i]) : '.');
                }

                oss << "  |" << text.str() << "|";

                log.dump(indent, MP4_LOG_VERBOSE2, "\"%s\": %s = <%u bytes>%s",
                         m_parentAtom.GetFile().GetFilename().c_str(),
                         m_name, size, oss.str().c_str() );
                return;
            }

            // specialization for ilst item data always show all bytes except for covr
            bool showall = false;
            MP4Atom *const datac = m_parentAtom.GetParentAtom(); // data container
            MP4Atom *const datacc = datac->GetParentAtom();
            if( datacc &&
                    ATOMID( datacc->GetType() ) == ATOMID( "ilst" ) &&
                    ATOMID( datac->GetType() ) != ATOMID( "covr" ) )
            {
                showall = true;
            }

            uint32_t adjsize;
            bool supressed;

            if( showall ||
                    size < 128 || log.verbosity >= MP4_LOG_VERBOSE2 )
            {
                adjsize = size;
                supressed = false;
            }
            else
            {
                adjsize = 128;
                supressed = true;
            }

            ostringstream oss;
            ostringstream text;

            log.dump(indent, MP4_LOG_VERBOSE2, "\"%s\": %s = <%u bytes>",
                     m_parentAtom.GetFile().GetFilename().c_str(),
                     m_name, size );
            log.hexDump(indent, MP4_LOG_VERBOSE2, value, adjsize, "\"%s\": %s",
                        m_parentAtom.GetFile().GetFilename().c_str(),
                        m_name);

            if( supressed )
            {
                log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": <remaining bytes supressed>",
                         m_parentAtom.GetFile().GetFilename().c_str() );
            }
        }

        // MP4TableProperty

        MP4TableProperty::MP4TableProperty(MP4Atom &parentAtom, const char *name, MP4IntegerProperty *pCountProperty)
            : MP4Property(parentAtom, name)
        {
            m_pCountProperty = pCountProperty;
            m_pCountProperty->SetReadOnly();
        }

        MP4TableProperty::~MP4TableProperty()
        {
            for (uint32_t i = 0; i < m_pProperties.Size(); i++)
            {
                delete m_pProperties[i];
            }
        }

        void MP4TableProperty::AddProperty(MP4Property *pProperty)
        {
            ASSERT(pProperty);
            ASSERT(pProperty->GetType() != TableProperty);
            ASSERT(pProperty->GetType() != DescriptorProperty);
            m_pProperties.Add(pProperty);
            pProperty->SetCount(0);
        }

        bool MP4TableProperty::FindProperty(const char *name,
                                            MP4Property **ppProperty, uint32_t *pIndex)
        {
            ASSERT(m_name);

            // check if first component of name matches ourselves
            if (!MP4NameFirstMatches(m_name, name))
            {
                return false;
            }

            // check if the specified table entry exists
            uint32_t index;
            bool haveIndex = MP4NameFirstIndex(name, &index);
            if (haveIndex)
            {
                if (index >= GetCount())
                {
                    return false;
                }
                if (pIndex)
                {
                    *pIndex = index;
                }
            }

            log.verbose1f("\"%s\": FindProperty: matched %s",
                          m_parentAtom.GetFile().GetFilename().c_str(), name);

            // get name of table property
            const char *tablePropName = MP4NameAfterFirst(name);
            if (tablePropName == NULL)
            {
                if (!haveIndex)
                {
                    *ppProperty = this;
                    return true;
                }
                return false;
            }

            // check if this table property exists
            return FindContainedProperty(tablePropName, ppProperty, pIndex);
        }

        bool MP4TableProperty::FindContainedProperty(const char *name,
                MP4Property **ppProperty, uint32_t *pIndex)
        {
            uint32_t numProperties = m_pProperties.Size();

            for (uint32_t i = 0; i < numProperties; i++)
            {
                if (m_pProperties[i]->FindProperty(name, ppProperty, pIndex))
                {
                    return true;
                }
            }
            return false;
        }

        void MP4TableProperty::Read(MP4File &file, uint32_t index)
        {
            ASSERT(index == 0);

            if (m_implicit)
            {
                return;
            }

            uint32_t numProperties = m_pProperties.Size();

            if (numProperties == 0)
            {
                WARNING(numProperties == 0);
                return;
            }

            uint32_t numEntries = GetCount();

            /* for each property set size */
            for (uint32_t j = 0; j < numProperties; j++)
            {
                m_pProperties[j]->SetCount(numEntries);
            }

            for (uint32_t i = 0; i < numEntries; i++)
            {
                ReadEntry(file, i);
            }
        }

        void MP4TableProperty::ReadEntry(MP4File &file, uint32_t index)
        {
            for (uint32_t j = 0; j < m_pProperties.Size(); j++)
            {
                m_pProperties[j]->Read(file, index);
            }
        }

        void MP4TableProperty::Write(MP4File &file, uint32_t index)
        {
            ASSERT(index == 0);

            if (m_implicit)
            {
                return;
            }

            uint32_t numProperties = m_pProperties.Size();

            if (numProperties == 0)
            {
                WARNING(numProperties == 0);
                return;
            }

            uint32_t numEntries = GetCount();

            if (m_pProperties[0]->GetCount() != numEntries)
            {
                log.errorf("%s: \"%s\": %s %s \"%s\"table entries %u doesn't match count %u",
                           __FUNCTION__, m_parentAtom.GetFile().GetFilename().c_str(),
                           GetParentAtom().GetType(),
                           GetName(), m_pProperties[0]->GetName(),
                           m_pProperties[0]->GetCount(), numEntries);

                ASSERT(m_pProperties[0]->GetCount() == numEntries);
            }

            for (uint32_t i = 0; i < numEntries; i++)
            {
                WriteEntry(file, i);
            }
        }

        void MP4TableProperty::WriteEntry(MP4File &file, uint32_t index)
        {
            for (uint32_t j = 0; j < m_pProperties.Size(); j++)
            {
                m_pProperties[j]->Write(file, index);
            }
        }

        void MP4TableProperty::Dump(uint8_t indent,
                                    bool dumpImplicits, uint32_t index)
        {
            ASSERT(index == 0);

            // implicit tables just can't be dumped
            if (m_implicit)
            {
                return;
            }

            uint32_t numProperties = m_pProperties.Size();

            if (numProperties == 0)
            {
                WARNING(numProperties == 0);
                return;
            }

            uint32_t numEntries = GetCount();

            for (uint32_t i = 0; i < numEntries; i++)
            {
                for (uint32_t j = 0; j < numProperties; j++)
                {
                    m_pProperties[j]->Dump(indent + 1, dumpImplicits, i);
                }
            }
        }

        // MP4DescriptorProperty

        MP4DescriptorProperty::MP4DescriptorProperty(MP4Atom &parentAtom, const char *name,
                uint8_t tagsStart, uint8_t tagsEnd, bool mandatory, bool onlyOne)
            : MP4Property(parentAtom, name)
        {
            SetTags(tagsStart, tagsEnd);
            m_sizeLimit = 0;
            m_mandatory = mandatory;
            m_onlyOne = onlyOne;
        }

        MP4DescriptorProperty::~MP4DescriptorProperty()
        {
            for (uint32_t i = 0; i < m_pDescriptors.Size(); i++)
            {
                delete m_pDescriptors[i];
            }
        }

        MP4Descriptor *MP4DescriptorProperty::AddDescriptor(uint8_t tag)
        {
            // check that tag is in expected range
            ASSERT(tag >= m_tagsStart && tag <= m_tagsEnd);

            MP4Descriptor *pDescriptor = CreateDescriptor(m_parentAtom, tag);
            ASSERT(pDescriptor);

            m_pDescriptors.Add(pDescriptor);

            return pDescriptor;
        }

        void MP4DescriptorProperty::DeleteDescriptor(uint32_t index)
        {
            delete m_pDescriptors[index];
            m_pDescriptors.Delete(index);
        }

        void MP4DescriptorProperty::Generate()
        {
            // generate a default descriptor
            // if it is mandatory, and single
            if (m_mandatory && m_onlyOne)
            {
                MP4Descriptor *pDescriptor =
                    AddDescriptor(m_tagsStart);
                pDescriptor->Generate();
            }
        }

        bool MP4DescriptorProperty::FindProperty(const char *name,
                MP4Property **ppProperty, uint32_t *pIndex)
        {
            // we're unnamed, so just check contained properties
            if (m_name == NULL || !strcmp(m_name, ""))
            {
                return FindContainedProperty(name, ppProperty, pIndex);
            }

            // check if first component of name matches ourselves
            if (!MP4NameFirstMatches(m_name, name))
            {
                return false;
            }

            // check if the specific descriptor entry exists
            uint32_t descrIndex;
            bool haveDescrIndex = MP4NameFirstIndex(name, &descrIndex);

            if (haveDescrIndex && descrIndex >= GetCount())
            {
                return false;
            }

            log.verbose1f("\"%s\": matched %s",
                          m_parentAtom.GetFile().GetFilename().c_str(),
                          name);

            // get name of descriptor property
            name = MP4NameAfterFirst(name);
            if (name == NULL)
            {
                if (!haveDescrIndex)
                {
                    *ppProperty = this;
                    return true;
                }
                return false;
            }

            /* check rest of name */
            if (haveDescrIndex)
            {
                return m_pDescriptors[descrIndex]->FindProperty(name,
                        ppProperty, pIndex);
            }
            else
            {
                return FindContainedProperty(name, ppProperty, pIndex);
            }
        }

        bool MP4DescriptorProperty::FindContainedProperty(const char *name,
                MP4Property **ppProperty, uint32_t *pIndex)
        {
            for (uint32_t i = 0; i < m_pDescriptors.Size(); i++)
            {
                if (m_pDescriptors[i]->FindProperty(name, ppProperty, pIndex))
                {
                    return true;
                }
            }
            return false;
        }

        void MP4DescriptorProperty::Read(MP4File &file, uint32_t index)
        {
            ASSERT(index == 0);

            if (m_implicit)
            {
                return;
            }

            uint64_t start = file.GetPosition();

            while (true)
            {
                // enforce size limitation
                if (m_sizeLimit && file.GetPosition() >= start + m_sizeLimit)
                {
                    break;
                }

                uint8_t tag;
                try
                {
                    file.PeekBytes(&tag, 1);
                }
                catch (Exception *x)
                {
                    if (file.GetPosition() >= file.GetSize())
                    {
                        // EOF
                        delete x;
                        break;
                    }
                    throw x;
                }

                // check if tag is in desired range
                if (tag < m_tagsStart || tag > m_tagsEnd)
                {
                    break;
                }

                MP4Descriptor *pDescriptor =
                    AddDescriptor(tag);

                pDescriptor->Read(file);
            }

            // warnings
            if (m_mandatory && m_pDescriptors.Size() == 0)
            {
                log.warningf("%s: \"%s\": Mandatory descriptor 0x%02x missing",
                             __FUNCTION__, GetParentAtom().GetFile().GetFilename().c_str(), m_tagsStart);
            }
            else if (m_onlyOne && m_pDescriptors.Size() > 1)
            {
                log.warningf("%s: \"%s\": Descriptor 0x%02x has more than one instance",
                             __FUNCTION__, GetParentAtom().GetFile().GetFilename().c_str(), m_tagsStart);
            }
        }

        void MP4DescriptorProperty::Write(MP4File &file, uint32_t index)
        {
            ASSERT(index == 0);

            if (m_implicit)
            {
                return;
            }

            for (uint32_t i = 0; i < m_pDescriptors.Size(); i++)
            {
                m_pDescriptors[i]->Write(file);
            }
        }

        void MP4DescriptorProperty::Dump(uint8_t indent,
                                         bool dumpImplicits, uint32_t index)
        {
            ASSERT(index == 0);

            if (m_implicit && !dumpImplicits)
            {
                return;
            }

            if (m_name)
            {
                if (index != 0)
                    log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s[%u]",
                             m_parentAtom.GetFile().GetFilename().c_str(),
                             m_name, index);
                else
                    log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s",
                             m_parentAtom.GetFile().GetFilename().c_str(),
                             m_name);
                indent++;
            }

            for (uint32_t i = 0; i < m_pDescriptors.Size(); i++)
            {
                m_pDescriptors[i]->Dump(indent, dumpImplicits);
            }
        }

        ///////////////////////////////////////////////////////////////////////////////

        MP4LanguageCodeProperty::MP4LanguageCodeProperty( MP4Atom &parentAtom, const char *name, bmff::LanguageCode value )
            : MP4Property( parentAtom, name )
        {
            SetValue( value );
        }

        MP4LanguageCodeProperty::MP4LanguageCodeProperty( MP4Atom &parentAtom, const char *name, const string &code )
            : MP4Property( parentAtom, name )
        {
            SetValue( bmff::enumLanguageCode.toType( code ));
        }

        void
        MP4LanguageCodeProperty::Dump( uint8_t indent, bool dumpImplicits, uint32_t index )
        {
            uint16_t data = 0;

            string svalue;
            bmff::enumLanguageCode.toString( _value, svalue );
            if( svalue.length() == 3 )
            {
                data = (((svalue[0] - 0x60) & 0x001f) << 10)
                       | (((svalue[1] - 0x60) & 0x001f) <<  5)
                       | (((svalue[2] - 0x60) & 0x001f)      );
            }

            log.dump(indent, MP4_LOG_VERBOSE2, "\"%s\": %s = %s (0x%04x)",
                     m_parentAtom.GetFile().GetFilename().c_str(),
                     m_name, bmff::enumLanguageCode.toString( _value, true ).c_str(), data );
        }

        uint32_t
        MP4LanguageCodeProperty::GetCount()
        {
            return 1;
        }

        MP4PropertyType
        MP4LanguageCodeProperty::GetType()
        {
            return LanguageCodeProperty;
        }

        bmff::LanguageCode
        MP4LanguageCodeProperty::GetValue()
        {
            return _value;
        }

        void
        MP4LanguageCodeProperty::Read( MP4File &file, uint32_t index )
        {
            uint16_t data = file.ReadBits( 16 );

            char code[3];
            code[0] = ((data & 0x7c00) >> 10) + 0x60;
            code[1] = ((data & 0x03e0) >>  5) + 0x60;
            code[2] = ((data & 0x001f)      ) + 0x60;

            SetValue( bmff::enumLanguageCode.toType( string( code, sizeof(code) )));
        }

        void
        MP4LanguageCodeProperty::SetCount( uint32_t count )
        {
            // do nothing; count is always 1
        }

        void
        MP4LanguageCodeProperty::SetValue( bmff::LanguageCode value )
        {
            _value = value;
        }

        void
        MP4LanguageCodeProperty::Write( MP4File &file, uint32_t index )
        {
            uint16_t data = 0;

            string svalue;
            bmff::enumLanguageCode.toString( _value, svalue );
            if( svalue.length() == 3 )
            {
                data = (((svalue[0] - 0x60) & 0x001f) << 10)
                       | (((svalue[1] - 0x60) & 0x001f) <<  5)
                       | (((svalue[2] - 0x60) & 0x001f)      );
            }

            file.WriteBits( data, 16 );
        }

        ///////////////////////////////////////////////////////////////////////////////

        MP4BasicTypeProperty::MP4BasicTypeProperty( MP4Atom &parentAtom, const char *name, itmf::BasicType type )
            : MP4Property( parentAtom, name )
        {
            SetValue( type );
        }

        void
        MP4BasicTypeProperty::Dump( uint8_t indent, bool dumpImplicits, uint32_t index )
        {
            log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s = %s (0x%02x)",
                     m_parentAtom.GetFile().GetFilename().c_str(), m_name,
                     itmf::enumBasicType.toString( _value, true ).c_str(), _value );
        }

        uint32_t
        MP4BasicTypeProperty::GetCount()
        {
            return 1;
        }

        MP4PropertyType
        MP4BasicTypeProperty::GetType()
        {
            return BasicTypeProperty;
        }

        itmf::BasicType
        MP4BasicTypeProperty::GetValue()
        {
            return _value;
        }

        void
        MP4BasicTypeProperty::Read( MP4File &file, uint32_t index )
        {
            SetValue( static_cast<itmf::BasicType>( file.ReadBits( 8 )));
        }

        void
        MP4BasicTypeProperty::SetCount( uint32_t count )
        {
            // do nothing; count is always 1
        }

        void
        MP4BasicTypeProperty::SetValue( itmf::BasicType value )
        {
            _value = value;
        }

        void
        MP4BasicTypeProperty::Write( MP4File &file, uint32_t index )
        {
            file.WriteBits( _value, 8 );
        }

        ///////////////////////////////////////////////////////////////////////////////

    }
} // namespace mp4v2::impl
